/*
    curlplusplus - a c++ wrapper around libcurl
    Copyright (C) 2012  Francesco "Franc[e]sco" Noferi (francesco1149@gmail.com)

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the
    Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
    Boston, MA  02110-1301, USA.
*/

#pragma once

#include "curlpp.hpp"
#include <curl/curl.h>
#include <list>

namespace curlpp
{
    class easy; // forward declaration

    /**
     * Wraps libcurl's multi interface.
     */
    class multi
    {
    public:
        /**
         * Default constructor.
         *
         * @throw curlpp::exception an exception that describes the general cause of the error.
         */
        multi() throw(const exception &);

        /**
         * Default destructor.
         *
         * @throw curlpp::exception an exception that describes the general cause of the error.
         */
        virtual ~multi() throw(const exception &);

        /**
         * Adds an easy handle to the multi session.
         *
         * @param easy_handle the easy handle to be added.
         * @throw curlpp::exception an exception that describes the general cause of the error.
         * @see remove_handle()
         * @see get_handle()
         */
        void add_handle(easy *easy_handle) throw(const exception &);

        /**
         * Retrieves an easy handle from a CURL message.
         *
         * @param msg pointer to the curl message.
         * @return a pointer to the easy handle.
         *         If the msg does not belong to any of the handles in the multi instance, NULL is returned.
         */
        easy *get_handle(CURLMsg *msg) throw();

        /**
         * Removes an easy handle from the multi session.
         * When the easy handle has been removed from a multi stack, it is again perfectly legal
         * to invoke easy::perform() on this easy handle.
         * Removing an easy handle while being used, will effectively halt the transfer in progress
         * involving that easy handle. All other easy handles and transfers will remain unaffected.
         *
         * @param easy_handle the easy handle to be added.
         * @throw curlpp::exception an exception that describes the general cause of the error.
         * @see add_handle()
         */
        void remove_handle(easy *easy_handle) throw(const exception &);

        /**
         * Extracts file descriptor information from a given multi_handle.
         * See <a href="http://curl.haxx.se/libcurl/c/curl_multi_fdset.html">curl_multi_fdset</a> for
         * more info on the usage of this method.
         *
         * @param read_fd_set pointer to a fd_set struct that will store the read file descriptor
         * @param write_fd_set pointer to a fd_set struct that will store the write file descriptor
         * @param exc_fd_set pointer to a fd_set struct that will store the exc file descriptor
         * @param max_fd pointer to an integer which will contain -1 if no file descriptors are set
         *               by libcurl, otherwise it will contain the higher descriptor number libcurl set.
         */
        void fdset(fd_set *read_fd_set, fd_set *write_fd_set, fd_set *exc_fd_set, int *max_fd)
            throw(const exception &);

        /**
         * Sets a cURL option.
         * See <a href="http://curl.haxx.se/libcurl/c/curl_multi_setopt.html">curl_multi_setopt</a> for more info
         * about the available options.
         *
         * @param tag the option to set.
         * @param value the value to assign to the option.
         *
         * @throw curlpp::exception an exception that describes the general cause of the error.
         *
         * @see info_read()
         */
        template <typename T>
        void setopt(CURLMoption tag, T value) throw(const exception &)
        {
            attempt(curl_multi_setopt(curl, tag, value), "multi.setopt: failed to set option - ");
        }

        /**
         * Ask the multi handle if there are any messages/informationals from the individual transfers.
         * See <a href="http://curl.haxx.se/libcurl/c/curl_multi_info_read.html">curl_multi_info_read</a> for more info
         * on the usage of this method.
         *
         * @param msgs_in_queue pointer to the integer that will store the remaining messages after the call.
         * @throw curlpp::exception an exception that describes the general cause of the error.
         * @return a pointer to a CURLMsg struct or NULL if it fails or runs out of structs.
         * @see perform()
         */
        CURLMsg *info_read(int *msgs_in_queue) throw();

        /**
         * Reads/writes available data given an action. For information on the usage of this method, see
         * <a href="http://curl.haxx.se/libcurl/c/curl_multi_socket_action.html">curl_multi_socket_action</a>.
         *
         * @param sockfd socket with the action.
         * @param ev_bitmask the events on the sockets if known, otherwise zero.
         * @param running_handles pointer to an integer that will store the number of running easy
         *                        handles within the multi handle.
         *
         * @throw curlpp::exception an exception that describes the general cause of the error.
         *
         * @see fdset()
         * @see info_read()
         */
        void socket_action(curl_socket_t sockfd, int ev_bitmask, int *running_handles) throw(const exception &);

        /**
         * Reads/writes available data from each easy handle.
         * When the app thinks there's data available for the multi_handle, it should call this function to
         * read/write whatever there is to read or write right now. perform() returns as soon as the reads/writes
         * are done. This function does not require that there actually is any data available for reading or that
         * data can be written, it can be called just in case. It will write the number of handles that still
         * transfer data in the second argument's integer-pointer.
         * For more info, see <a href="http://curl.haxx.se/libcurl/c/curl_multi_perform.html">curl_multi_perform</a>.
         *
         * @param running_handles pointer to an integer that will store the number of handles still running.
         * @throw curlpp::exception an exception that describes the general cause of the error.
         * @return true if curl wants perform to be called again immediately, otherwise false.
         * @see setopt()
         * @see fdset()
         * @see info_read()
         */
        bool perform(int *running_handles) throw(const exception &);

        /**
         * Obtains the minimum amount of time to wait for socket actions before proceeding.
         * See <a href="http://curl.haxx.se/libcurl/c/curl_multi_timeout.html">curl_multi_timeout</a>
         * for more info.
         *
         * @param timeout pointer to a long that will store the milliseconds the program must wait. If zero, it means
         *                the program should proceed immediately, if -1 there is no timeout set.
         *
         * @throw curlpp::exception an exception that describes the general cause of the error.
         *
         * @see fdset()
         * @see info_read()
         * @see setopt()
         */
        void timeout(long *timeout);


    protected:
        CURLM *curl; /**< Internal cURL multi handle */
        std::list<easy *> handles; /**< Internal list of easy handles */

        /**
         * If the passed CURLMcode represents an error, throws an exception that describes
         * the error, otherwise does nothing.
         *
         * @param res the error code returned by a curl_multi function.
         * @param errortext a general description of the error. The curl error string will
         *                  be appended to this string.
         * @throw curlpp::exception an exception that describes the general cause of the error.
         */
        static void attempt(CURLMcode res, std::string errortext) throw(const exception &);
    };
}
